// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/common/inter_process_time_ticks_converter.h"

#include <stdint.h>

#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::TimeTicks;

namespace content {

namespace {

    struct TestParams {
        int64_t local_lower_bound;
        int64_t remote_lower_bound;
        int64_t remote_upper_bound;
        int64_t local_upper_bound;
        int64_t test_time;
        int64_t test_delta;
    };

    struct TestResults {
        int64_t result_time;
        int32_t result_delta;
        bool is_skew_additive;
        int64_t skew;
    };

    TestResults RunTest(const TestParams& params)
    {
        TimeTicks local_lower_bound = TimeTicks::FromInternalValue(
            params.local_lower_bound);
        TimeTicks local_upper_bound = TimeTicks::FromInternalValue(
            params.local_upper_bound);
        TimeTicks remote_lower_bound = TimeTicks::FromInternalValue(
            params.remote_lower_bound);
        TimeTicks remote_upper_bound = TimeTicks::FromInternalValue(
            params.remote_upper_bound);
        TimeTicks test_time = TimeTicks::FromInternalValue(params.test_time);

        InterProcessTimeTicksConverter converter(
            LocalTimeTicks::FromTimeTicks(local_lower_bound),
            LocalTimeTicks::FromTimeTicks(local_upper_bound),
            RemoteTimeTicks::FromTimeTicks(remote_lower_bound),
            RemoteTimeTicks::FromTimeTicks(remote_upper_bound));

        TestResults results;
        results.result_time = converter.ToLocalTimeTicks(
                                           RemoteTimeTicks::FromTimeTicks(
                                               test_time))
                                  .ToTimeTicks()
                                  .ToInternalValue();
        results.result_delta = converter.ToLocalTimeDelta(
                                            RemoteTimeDelta::FromRawDelta(params.test_delta))
                                   .ToInt32();
        results.is_skew_additive = converter.IsSkewAdditiveForMetrics();
        results.skew = converter.GetSkewForMetrics().ToInternalValue();
        return results;
    }

    TEST(InterProcessTimeTicksConverterTest, NullTime)
    {
        // Null / zero times should remain null.
        TestParams p;
        p.local_lower_bound = 1;
        p.remote_lower_bound = 2;
        p.remote_upper_bound = 5;
        p.local_upper_bound = 6;
        p.test_time = 0;
        p.test_delta = 0;
        TestResults results = RunTest(p);
        EXPECT_EQ(0, results.result_time);
        EXPECT_EQ(0, results.result_delta);
    }

    TEST(InterProcessTimeTicksConverterTest, NoSkew)
    {
        // All times are monotonic and centered, so no adjustment should occur.
        TestParams p;
        p.local_lower_bound = 1;
        p.remote_lower_bound = 2;
        p.remote_upper_bound = 5;
        p.local_upper_bound = 6;
        p.test_time = 3;
        p.test_delta = 1;
        TestResults results = RunTest(p);
        EXPECT_EQ(3, results.result_time);
        EXPECT_EQ(1, results.result_delta);
        EXPECT_TRUE(results.is_skew_additive);
        EXPECT_EQ(0, results.skew);
    }

    TEST(InterProcessTimeTicksConverterTest, OffsetMidpoints)
    {
        // All times are monotonic, but not centered. Adjust the |remote_*| times so
        // they are centered within the |local_*| times.
        TestParams p;
        p.local_lower_bound = 1;
        p.remote_lower_bound = 3;
        p.remote_upper_bound = 6;
        p.local_upper_bound = 6;
        p.test_time = 4;
        p.test_delta = 1;
        TestResults results = RunTest(p);
        EXPECT_EQ(3, results.result_time);
        EXPECT_EQ(1, results.result_delta);
        EXPECT_TRUE(results.is_skew_additive);
        EXPECT_EQ(1, results.skew);
    }

    TEST(InterProcessTimeTicksConverterTest, DoubleEndedSkew)
    {
        // |remote_lower_bound| occurs before |local_lower_bound| and
        // |remote_upper_bound| occurs after |local_upper_bound|. We must adjust both
        // bounds and scale down the delta. |test_time| is on the midpoint, so it
        // doesn't change. The ratio of local time to network time is 1:2, so we scale
        // |test_delta| to half.
        TestParams p;
        p.local_lower_bound = 3;
        p.remote_lower_bound = 1;
        p.remote_upper_bound = 9;
        p.local_upper_bound = 7;
        p.test_time = 5;
        p.test_delta = 2;
        TestResults results = RunTest(p);
        EXPECT_EQ(5, results.result_time);
        EXPECT_EQ(1, results.result_delta);
        EXPECT_FALSE(results.is_skew_additive);
    }

    TEST(InterProcessTimeTicksConverterTest, FrontEndSkew)
    {
        // |remote_upper_bound| is coherent, but |remote_lower_bound| is not. So we
        // adjust the lower bound and move |test_time| out. The scale factor is 2:3,
        // but since we use integers, the numbers truncate from 3.33 to 3 and 1.33
        // to 1.
        TestParams p;
        p.local_lower_bound = 3;
        p.remote_lower_bound = 1;
        p.remote_upper_bound = 7;
        p.local_upper_bound = 7;
        p.test_time = 3;
        p.test_delta = 2;
        TestResults results = RunTest(p);
        EXPECT_EQ(4, results.result_time);
        EXPECT_EQ(1, results.result_delta);
        EXPECT_FALSE(results.is_skew_additive);
    }

    TEST(InterProcessTimeTicksConverterTest, BackEndSkew)
    {
        // Like the previous test, but |remote_lower_bound| is coherent and
        // |remote_upper_bound| is skewed.
        TestParams p;
        p.local_lower_bound = 1;
        p.remote_lower_bound = 1;
        p.remote_upper_bound = 7;
        p.local_upper_bound = 5;
        p.test_time = 3;
        p.test_delta = 2;
        TestResults results = RunTest(p);
        EXPECT_EQ(2, results.result_time);
        EXPECT_EQ(1, results.result_delta);
        EXPECT_FALSE(results.is_skew_additive);
    }

    TEST(InterProcessTimeTicksConverterTest, Instantaneous)
    {
        // The bounds are all okay, but the |remote_lower_bound| and
        // |remote_upper_bound| have the same value. No adjustments should be made and
        // no divide-by-zero errors should occur.
        TestParams p;
        p.local_lower_bound = 1;
        p.remote_lower_bound = 2;
        p.remote_upper_bound = 2;
        p.local_upper_bound = 3;
        p.test_time = 2;
        p.test_delta = 0;
        TestResults results = RunTest(p);
        EXPECT_EQ(2, results.result_time);
        EXPECT_EQ(0, results.result_delta);
    }

    TEST(InterProcessTimeTicksConverterTest, OffsetInstantaneous)
    {
        // The bounds are all okay, but the |remote_lower_bound| and
        // |remote_upper_bound| have the same value and are offset from the midpoint
        // of |local_lower_bound| and |local_upper_bound|. An offset should be applied
        // to make the midpoints line up.
        TestParams p;
        p.local_lower_bound = 1;
        p.remote_lower_bound = 3;
        p.remote_upper_bound = 3;
        p.local_upper_bound = 3;
        p.test_time = 3;
        p.test_delta = 0;
        TestResults results = RunTest(p);
        EXPECT_EQ(2, results.result_time);
        EXPECT_EQ(0, results.result_delta);
    }

    TEST(InterProcessTimeTicksConverterTest, DisjointInstantaneous)
    {
        // |local_lower_bound| and |local_upper_bound| are the same. No matter what
        // the other values are, they must fit within [local_lower_bound,
        // local_upper_bound].  So, all of the values should be adjusted so they are
        // exactly that value.
        TestParams p;
        p.local_lower_bound = 1;
        p.remote_lower_bound = 2;
        p.remote_upper_bound = 2;
        p.local_upper_bound = 1;
        p.test_time = 2;
        p.test_delta = 0;
        TestResults results = RunTest(p);
        EXPECT_EQ(1, results.result_time);
        EXPECT_EQ(0, results.result_delta);
    }

    TEST(InterProcessTimeTicksConverterTest, RoundingNearEdges)
    {
        // Verify that rounding never causes a value to appear outside the given
        // |local_*| range.
        const int kMaxRange = 101;
        for (int i = 1; i < kMaxRange; ++i) {
            for (int j = 1; j < kMaxRange; ++j) {
                TestParams p;
                p.local_lower_bound = 1;
                p.remote_lower_bound = 1;
                p.remote_upper_bound = j;
                p.local_upper_bound = i;

                p.test_time = 1;
                p.test_delta = 0;
                TestResults results = RunTest(p);
                EXPECT_LE(1, results.result_time);
                EXPECT_EQ(0, results.result_delta);

                p.test_time = j;
                p.test_delta = j - 1;
                results = RunTest(p);
                EXPECT_GE(i, results.result_time);
                EXPECT_GE(i - 1, results.result_delta);
            }
        }
    }

    TEST(InterProcessTimeTicksConverterTest, DisjointRanges)
    {
        TestParams p;
        p.local_lower_bound = 10;
        p.remote_lower_bound = 30;
        p.remote_upper_bound = 41;
        p.local_upper_bound = 20;
        p.test_time = 41;
        p.test_delta = 0;
        TestResults results = RunTest(p);
        EXPECT_EQ(20, results.result_time);
        EXPECT_EQ(0, results.result_delta);
    }

    TEST(InterProcessTimeTicksConverterTest, ValuesOutsideOfRange)
    {
        InterProcessTimeTicksConverter converter(
            LocalTimeTicks::FromTimeTicks(TimeTicks::FromInternalValue(15)),
            LocalTimeTicks::FromTimeTicks(TimeTicks::FromInternalValue(20)),
            RemoteTimeTicks::FromTimeTicks(TimeTicks::FromInternalValue(10)),
            RemoteTimeTicks::FromTimeTicks(TimeTicks::FromInternalValue(25)));

        RemoteTimeTicks remote_ticks = RemoteTimeTicks::FromTimeTicks(TimeTicks::FromInternalValue(10));
        int64_t result = converter.ToLocalTimeTicks(remote_ticks).ToTimeTicks().ToInternalValue();
        EXPECT_EQ(15, result);

        remote_ticks = RemoteTimeTicks::FromTimeTicks(TimeTicks::FromInternalValue(25));
        result = converter.ToLocalTimeTicks(remote_ticks).ToTimeTicks().ToInternalValue();
        EXPECT_EQ(20, result);

        remote_ticks = RemoteTimeTicks::FromTimeTicks(TimeTicks::FromInternalValue(9));
        result = converter.ToLocalTimeTicks(remote_ticks).ToTimeTicks().ToInternalValue();
        EXPECT_EQ(14, result);
    }

} // anonymous namespace

} // namespace content
